///////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2007-2009 ILOG, S.A.
//  All Rights Reserved.
//  The following is ILOG Source Code.  No modifications may be made to the  
//  ILOG Source Code.  Any usage of the ILOG Source Code is subject to 
//  the terms and conditions of the ILOG End User License Agreement   
//  applicable to this ILOG software product.
//
///////////////////////////////////////////////////////////////////////////////

package ilog.gauges.rectangular.renderers
{
  import flash.display.DisplayObject;
  import flash.display.Graphics;
  import flash.events.Event;
  import flash.geom.Rectangle;
  import flash.text.TextLineMetrics;
  
  import ilog.gauges.RectangleTickRenderer;
  import ilog.gauges.TickItem;
  import ilog.gauges.gaugesClasses.rectangular.RectangularScaleRelatedRenderer;
  import ilog.utils.CSSUtil;
  import ilog.utils.InstanceCache;
  import ilog.utils.MathUtil;
  
  import mx.core.ClassFactory;
  import mx.core.ContextualClassFactory;
  import mx.core.FlexShape;
  import mx.core.IDataRenderer;
  import mx.core.IFactory;
  import mx.core.IFlexDisplayObject;
  import mx.core.UIComponent;
  import mx.core.UITextField;
  import mx.core.UITextFormat;
  import mx.managers.ILayoutManagerClient;
  import mx.styles.CSSStyleDeclaration;
  import mx.styles.ISimpleStyleClient;
  
  /**
   * Specifies whether labels appear along the scale. 
   *  
   * @default true 
   */
  [Style(name="showLabels", type="Boolean", inherit="no")]
  
  /**
   * Specifies a gap to add to the label position.
   * 
   * @default 0
   */
  [Style(name="labelGap", type="Number", inherit="no")]
  
  /**
   * Specifies whether only minimum and maximum labels are shown. 
   *  
   * @default false 
   */
  [Style(name="onlyMinMaxLabels", type="Boolean", inherit="no")]  
  
  /**
   * Specifies whether major ticks appear along the scale. 
   *  
   * @default true 
   */
  [Style(name="showMajorTicks", type="Boolean", inherit="no")]

  /**
   * Specifies whether minor ticks appear along the scale. 
   *  
   * @default true 
   */
  [Style(name="showMinorTicks", type="Boolean", inherit="no")] 
      
  /**
   * A style name to be used for the major ticks renderers. Useful
   * for tick renderers that might use properties from this
   * style name. If the style name references a CSS selector the major
   * tick renderer must extends <code>UIComponent</code> or 
   * <code>TickRendererBase</code>.
   * 
   * @default null
   */
  [Style(name="majorTickStyleName", type="String", inherit="no")]

  /**
   * A style name to be used for the minor ticks renderers. Useful
   * for tick renderers that might use properties from this
   * style name. If the style name references a CSS selector the minor
   * tick renderer must extends <code>UIComponent</code> or 
   * <code>TickRendererBase</code>.
   *
   * @default null   
   */
  [Style(name="minorTickStyleName", type="String", inherit="no")]

  /**
   * The alpha level of the color defined by the <code>backgroundColor</code>
   * property.
   * Valid values range from 0.0 to 1.0.
   *  
   * @default 1.0
   */
  [Style(name="backgroundAlpha", type="Number", inherit="no")]
  
  /**
   * The background color of the <code>RectangularScaleRenderer</code>. The background
   * is drawn only behind the tick area of the scale not behind the label area. The value <code>undefined</code> 
   * means no background is drawn.
   * 
   * @default undefined   
   */
  [Style(name="backgroundColor", type="uint", format="Color", inherit="no")]

  /**
   * You use the <code>RectangularScaleRenderer</code> class to describe
   * the renderering of a gauge's scale. 
   * A scale renderer is responsible for rendering
   * the labels, major tick marks, and minor tick marks.
   * <p>   
   * By default, the <code>RectangularScaleRenderer</code> is the master of the
   * logical scale it relates to.
   * </p> 
   * <p>
   * The scale is made of two areas: the tick area and the label area. The
   * label area size is computed so that the labels can fit in it. The
   * remaining part of the scale is called the tick area.
   * </p>
   * Major ticks are controlled by the following properties:
   * <ul>
   *     <li> <code>majorTickRenderer</code> </li>
   *     <li> <code>majorTickWidth</code> (expressed in pixels or in a percentage of the tick area)</li>
   *     <li> <code>majorTickLength</code> (expressed in pixels or in a percentage of the tick area)</li>
   * </ul>
   *      
   * Minor ticks are controlled by the following properties:
   * <ul>
   *     <li> <code>minorTickRenderer</code> </li> 
   *     <li> <code>minorTickWidth</code> (expressed in pixels or in a percentage of the tick area)</li>
   *     <li> <code>minorTickLength</code> (expressed in pixels or in a percentage of the tick area)</li>
   * </ul>
   * <p>
   * The placement of the ticks is controlled by the <code>tickPlacement</code> property.
   * You can control the style of the ticks by specifying a <code>minorTickStyleName</code> 
   * and a <code>majorTickStyleName</code>.
   * </p>
   * Labels are controlled by the following properties:
   * <ul>
   *     <li> the various font style properties </li>
   *     <li> <code>labelPlacement</code> (trailing|leading).</li>
   *     <li> <code>labelFunction</code>.</li>
   * </ul>
   * <p>
   * <code>RectangularScaleRenderer</code> objects inherit the text format of the labels 
   * from the enclosing gauge object.
   * </p>
   * <p>
   * The <code>RectangularScaleRenderer</code> is able to render any of the rectangular-specific scales:
   * <code>RectangularLinearScale</code>, <code>RectangularLogScale</code>, and 
   * <code>RectangularCategoryScale</code>.
   * </p>
   * If no renderer is specified for major or minor ticks, the <code>RectangleTickRenderer</code> is used.
   *
   * @mxml
   *  
   * <p>The <code>&lt;ilog:RectangularScaleRenderer&gt;</code> tag inherits all the properties
   * of its parent classes and adds the following properties:</p>
   *  
   * <pre>
   * &lt;ilog:RectangularScaleRenderer
   *   <strong>Properties</strong>
   *   tickPlacement="middle|trailing|leading"
   *   majorTickRenderer="<i>IFactory</i>"
   *   majorTickWidth=<i>"0.5%"</i>
   *   majorTickLength=<i>"100%"</i>
   *   minorTickRenderer="<i>IFactory</i>"
   *   minorTickWidth=<i>"0.1%"</i>
   *   minorTickLength=<i>"50%"</i>
   *   labelPlacement="trailing|leading"
   *   clipTicks="false|true"
   *    
   *   <strong>Styles</strong>
   *   showLabels="true|false"
   *   showMajorTicks="true|false"
   *   showMinorTicks="true|false" 
   *   onlyMinMaxLabels="false|true"
   *   majorTickStyleName="<i>No default</i>"
   *   minorTickStyleName="<i>No default</i>" 
   * /&gt;
   * </pre>
   *    
   * @see ilog.gauges.RectangleTickRenderer
   * @see ilog.gauges.gaugesClasses.RectangularScaleRelatedRenderer#master
   * @see #tickAreaBounds
   * 
   * @includeExample examples/RectangularScaleRendererExample.mxml -noswf
   */              
  public class RectangularScaleRenderer extends RectangularScaleRelatedRenderer
  {
    private static var stylesInited:Boolean = initStyles();
        
    private static function initStyles():Boolean
    {
      var scaleStyle:CSSStyleDeclaration =
        CSSUtil.createSelector("RectangularScaleRenderer");
        
      scaleStyle.defaultFactory = function():void {
        this.majorTickStyleName = null;
        this.minorTickStyleName = null;  
        this.showLabels = true;
        this.onlyMinMaxLabels = false;
        this.backgroundAlpha = 1;
        this.backgroundColor = undefined;
        this.showMajorTicks = true;
        this.showMinorTicks = true;        
        this.labelGap = 0;
        this.textAlign = "center";
        this.paddingLeft = 1;
        this.paddingRight = 1;
      }

      return true;
    }  
 
    private var _majorTicksCache:InstanceCache = null;
    private var _minorTicksCache:InstanceCache = null;
    private var _labelsCache:InstanceCache = null;
    private var _measuringLabel:IFlexDisplayObject = null;
    private var _tickAreaBounds:Rectangle;
    private var _externalAreaBounds:Rectangle;
    private var _ticksHolder:UIComponent;
    private var _maskShape:FlexShape;    
    
    /**
     * Constructor.
     */
    public function RectangularScaleRenderer()
    {
      master = true;
    } 

    private static var textFieldFactory:ContextualClassFactory;

    private var _labelRenderer:IFactory = null;
    
    [Inspectable(category="General")]
    
    /** 
     *  A reference to the factory used to render the scale labels. 
     *  This type must implement the <code>IDataRenderer</code>
     *  and <code>IFlexDisplayObject</code> interfaces.
     *  <p>The <code>RectangularScaleRenderer</code> will create one instance of this class
     *  for each major tick on the scale.
     *  The <code>data</code> property of the <code>labelRenderer</code> is assigned a String 
     *  object containing the label to be rendered.</p>
     *  @since ILOG Elixir 2.0
     *  @ilog.reviewed SH 27 July 08
     */
    public function get labelRenderer():IFactory
    {
        return _labelRenderer;
    }

    /**
     *  @private
     */ 
    public function set labelRenderer(value:IFactory):void
    {
        if (_labelRenderer == value)
            return;
        _labelRenderer = value;
        
        if (_labelsCache == null) {
          _labelsCache = new InstanceCache(getLabelRenderer(), this);
        }
        else
        {
          _labelsCache.count = 0;
          _labelsCache.factory = getLabelRenderer();
        }
        
        _measuringLabel = null;
        invalidateSize();
        invalidateProperties();
        invalidateDisplayList();
    }

    private function getLabelRenderer():IFactory
    {
      if (_labelRenderer == null) 
        return textFieldFactory;
      else 
        return _labelRenderer;
    }

    private var _percentMajorTickLength:Number = 100;

    [Inspectable(environment="none")]
    
    /**
     * Number that specifies the length of major ticks as a percentage
     * of the scale tick area height (for horizontal gauges) or tick area width (for
     * vertical gauges). Allowed values are 0-100.
     * 
     * @default 100
     * @see #majorTickLength
     */
    public function get percentMajorTickLength():Number
    {
      return _percentMajorTickLength;
    }

    /**
     * @private
     */
    public function set percentMajorTickLength(value:Number):void
    {
      _percentMajorTickLength = value;
      if (!isNaN(_percentMajorTickLength))
        _majorTickLength = NaN; 
      invalidateDisplayList();      
    }

    private var _majorTickLength:Number = NaN;

    [Inspectable(category="Gauge")]
    [PercentProxy("percentMajorTickLength")]

    /**
     * Number that specifies the length of major ticks, in pixels. 
     *
     * <p><strong>Note:</strong> You can specify a percentage value in the MXML
     * <code>majorTickLength</code> attribute, such as <code>majorTickLength="50%"</code>,
     * but you cannot use a percentage value in the <code>majorTickLength</code>
     * property in ActionScript.
     * Use the <code>percentMajorTickLength</code> property instead.</p>
     *
     * @default NaN
     * @see #percentMajorTickLength 
     */
    public function get majorTickLength():Number
    {
      return _majorTickLength;
    }

    /**
     * @private
     */
    public function set majorTickLength(value:Number):void
    {
      _majorTickLength = value;
      if (!isNaN(_majorTickLength))
        _percentMajorTickLength = NaN;
      invalidateDisplayList();      
    }

    private var _percentMinorTickLength:Number = 50;

    [Inspectable(environment="none")]
    
    /**
     * Number that specifies the length of minor ticks as a percentage
     * of the scale tick area height (for horizontal gauges) or tick area width (for
     * vertical gauges). Allowed values are 0-100.
     * 
     * @default 50
     * @see #minorTickLength
     */
    public function get percentMinorTickLength():Number
    {
      return _percentMinorTickLength;
    }

    /**
     * @private
     */
    public function set percentMinorTickLength(value:Number):void
    {
      _percentMinorTickLength = value;
      if (!isNaN(_percentMinorTickLength))
        _minorTickLength = NaN;
      invalidateDisplayList();         
    }

    private var _minorTickLength:Number = NaN;

    [Inspectable(category="Gauge")]
    [PercentProxy("percentMinorTickLength")]

    /**
     * Number that specifies the length of minor ticks, in pixels. 
     *
     * <p><strong>Note:</strong> You can specify a percentage value in the MXML
     * <code>minorTickLength</code> attribute, such as <code>minorTickLength="50%"</code>,
     * but you cannot use a percentage value in the <code>minorTickLength</code>
     * property in ActionScript.
     * Use the <code>percentMinorTickLength</code> property instead.</p>
     *
     * @default NaN
     * @see #percentMinorTickLength 
     */
    public function get minorTickLength():Number
    {
      return _minorTickLength;
    }

    /**
     * @private
     */
    public function set minorTickLength(value:Number):void
    {
      _minorTickLength = value;
      if (!isNaN(_minorTickLength)) 
        _percentMinorTickLength = NaN;
      invalidateDisplayList();      
    }

    private var _percentMajorTickWidth:Number = 0.5;

    [Inspectable(environment="none")]
    
    /**
     * Number that specifies the width of major ticks as a percentage
     * of the scale tick area width (for horizontal gauges) or tick area height (for
     * vertical gauges). Allowed values are 0-100.
     * 
     * @default 0.5
     * @see #majorTickWidth
     */
    public function get percentMajorTickWidth():Number
    {
      return _percentMajorTickWidth;
    }   

    /**
     * @private
     */
    public function set percentMajorTickWidth(value:Number):void
    {
      _percentMajorTickWidth = value;
      if (!isNaN(_percentMajorTickWidth)) 
        _majorTickWidth = NaN;
      invalidateDisplayList();         
    }

    private var _majorTickWidth:Number = NaN;

    [Inspectable(category="Gauge")]
    [PercentProxy("percentMajorTickWidth")]

    /**
     * Number that specifies the width of major ticks, in pixels. 
     *
     * <p><strong>Note:</strong> You can specify a percentage value in the MXML
     * <code>majorTickWidth</code> attribute, such as <code>majorTickWidth="2%"</code>,
     * but you cannot use a percentage value in the <code>majorTickWidth</code>
     * property in ActionScript.
     * Use the <code>percentMajorTickWidth</code> property instead.</p>
     *
     * @default NaN
     * @see #percentMajorTickWidth
     */
    public function get majorTickWidth():Number
    {
      return _majorTickWidth;
    }

    /**
     * @private
     */
    public function set majorTickWidth(value:Number):void
    {
      _majorTickWidth = value;
      if (!isNaN(_majorTickWidth))
        _percentMajorTickWidth = NaN;
      invalidateDisplayList();      
    }

    private var _percentMinorTickWidth:Number = 0.1;

    [Inspectable(environment="none")]
    
    /**
     * Number that specifies the width of minor ticks as a percentage
     * of the scale tick area width (for horizontal gauges) or tick area height (for
     * vertical gauges). Allowed values are 0-100.
     * 
     * @default 0.1
     * @see #minorTickWidth
     */
    public function get percentMinorTickWidth():Number
    {
      return _percentMinorTickWidth;
    }

    /**
     * @private
     */
    public function set percentMinorTickWidth(value:Number):void
    {
      _percentMinorTickWidth = value;
      if (!isNaN(_percentMinorTickWidth))
        _minorTickWidth = NaN;
      invalidateDisplayList();         
    }

    private var _minorTickWidth:Number = NaN;

    [Inspectable(category="Gauge")]
    [PercentProxy("percentMinorTickWidth")]

    /**
     * Number that specifies the width of minor ticks, in pixels. 
     *
     * <p><strong>Note:</strong> You can specify a percentage value in the MXML
     * <code>minorTickWidth</code> attribute, such as <code>minorTickWidth="1%"</code>,
     * but you cannot use a percentage value in the <code>minorTickWidth</code>
     * property in ActionScript.
     * Use the <code>percentMinorTickWidth</code> property instead.</p>
     *
     * @default NaN
     * @see #percentMinorTickWidth
     */
    public function get minorTickWidth():Number
    {
      return _minorTickWidth;
    }

    /**
     * @private
     */
    public function set minorTickWidth(value:Number):void
    {
      _minorTickWidth = value;
      if (!isNaN(_minorTickWidth))
        _percentMinorTickWidth = NaN;      
      invalidateDisplayList();      
    }    

    private var _tickPlacement:String = "middle";
    
    [Inspectable(category="Gauge", defaultValue="middle", enumeration="leading,middle,trailing", type="String")]
    
    /**
     * Specifies where to draw the tick marks (major and minor). Options are:
     *  <ul>
     *   <li><code>"middle"</code> (default) -
     *   Draw the tick marks in the middle of the tick area.</li>
     *
     *   <li><code>"leading"</code> -
     *   Draw the tick marks in the leading part of the tick area (top part if the
     *   gauge is horizontal, left part if the gauge is vertical).</li>
     *
     *   <li><code>"trailing"</code> -
     *   Draw the tick marks in the trailing part of the tick area (bottom part if the
     *   gauge is horizontal, right part if the gauge is vertical).</li>
     * </ul>
     * 
     * @default "middle"
     * @see #tickAreaBounds
     */      
    public function get tickPlacement():String
    {
      return _tickPlacement;
    }

    /**
     * @private
     */
    public function set tickPlacement(value:String):void
    {
      _tickPlacement = value;
      invalidateDisplayList();
    }
    
    private var _majorTickRenderer:IFactory = new ClassFactory(RectangleTickRenderer);
    
    [Inspectable(category="Gauge")]
    
    /**
     * A factory that corresponds to the class used by the scale to represent major ticks on the scale. 
     * This class is instantiated once for each tick in the scale. Classes used as a 
     * <code>majorTickRenderer</code> must implement the <code>IFlexDisplayObject</code>, 
     * <code>ISimpleStyleClient</code>, and optionally the <code>IDataRenderer</code> interfaces. 
     * 
     * @default ilog.gauges.RectangleTickRenderer
     */
    public function get majorTickRenderer():IFactory 
    {
      return _majorTickRenderer;
    }

    /**
     * @private
     */
    public function set majorTickRenderer(renderer:IFactory):void 
    {
      _majorTickRenderer = renderer;
      invalidateDisplayList();
    }
    
    private var _minorTickRenderer:IFactory = new ClassFactory(RectangleTickRenderer);
    
    [Inspectable(category="Gauge")]
    
    /**
     * A factory that corresponds to the class the scale uses to represent minor ticks on the scale. 
     * This class is instantiated once for each tick in the scale. Classes used as a 
     * <code>minorTickRenderer</code> must implement the <code>IFlexDisplayObject</code>, 
     * <code>ISimpleStyleClient</code>, and <code>IDataRenderer</code> interfaces. 
     * 
     * @default ilog.gauges.RectangleTickRenderer
     */
    public function get minorTickRenderer():IFactory 
    {
      return _minorTickRenderer;
    }

    /**
     * @private
     */
    public function set minorTickRenderer(renderer:IFactory):void 
    {
      _minorTickRenderer = renderer;
      invalidateDisplayList();
    }
              
    /**
     *  @private
     *  Storage for the labelFunction property.
     */
    private var _labelFunction:Function = null;

    [Inspectable(category="Gauge")]
    
    /**
     * Called to format scale values to be displayed as labels.
     * A <code>labelFunction</code> has the following signature:
     * <pre>
     *  function labelFunction(item:TickItem):String
     * </pre>
     *  
     * @default null  
     */
    public function get labelFunction():Function
    {
      return _labelFunction;
    }
    
    /**
     * @private
     */
    public function set labelFunction(value:Function):void
    {
      _labelFunction = value;
      invalidateDisplayList();         
    }
    
    private var _labelPlacement:String = "trailing";

    [Inspectable(category="Gauge", defaultValue="trailing", enumeration="leading,trailing", type="String")]
    
    /**
     * Specifies where to draw labels. Options are:
     *  <ul>
     *   <li><code>"trailing"</code> (default)-
     *   Draw the labels at the bottom of the tick area if the gauge is horizontal,
     *   to the right of the tick area if the gauge is vertical.</li>
     *
     *   <li><code>"leading"</code> -
     *   Draw the labels at the top of the tick area if the gauge is horizontal,
     *   to the left of the tick area if the gauge is vertical.</li>
     * </ul>
     * 
     * @default "trailing"
     * @see #tickAreaBounds
     */
    public function get labelPlacement():String
    {
      return _labelPlacement;
    }

    /**
     * @private
     */
    public function set labelPlacement(value:String):void
    {
      _labelPlacement = value;
      invalidateDisplayList();
    }        

    private function defaultLabelFunction(item:TickItem):String
    {
      if (item.value is Number)
        return MathUtil.truncate(Number(item.value), 2).toString();
      if (item.value is String)
        return String(item.value);
      return "";
    }        
        
    private function getMajorTickWidth():Number
    {
      var ret:Number;
      if (isNaN(majorTickWidth))
        ret = (gauge.direction == "horizontal" ? unscaledWidth : unscaledHeight) * percentMajorTickWidth / 100;
      else
        ret = majorTickWidth;
      return ret;
    }    

    private function getMajorTickLength():Number
    {
      var ret:Number;
      if (isNaN(majorTickLength))
        ret = (gauge.direction == "horizontal" ? _tickAreaBounds.height : 
          _tickAreaBounds.width) * percentMajorTickLength / 100;
      else
        ret = majorTickLength;
      return ret;
    }           

    private function getMinorTickWidth():Number
    {
      var ret:Number;
      if (isNaN(minorTickWidth))
        ret = (gauge.direction == "horizontal" ? unscaledWidth : unscaledHeight) * percentMinorTickWidth / 100;
      else
        ret = minorTickWidth;
      return ret;
    }

    private function getMinorTickLength():Number
    {
      var ret:Number;
      if (isNaN(minorTickLength))
        ret = (gauge.direction == "horizontal" ? _tickAreaBounds.height : 
          _tickAreaBounds.width) * percentMinorTickLength / 100;
      else
        ret = minorTickLength;
      return ret;
    }  
    
    private var _clipTicks:Boolean = false;
    
    [Inspectable(category="Gauge")]    
    
    /**
     * Specifies whether the first and last major ticks are clipped so that 
     * no ticks are drawn before the position of the minimum value nor after the
     * position of the maximum value. This is particularly useful when you want
     * to use a <code>RectangularBarRenderer</code> with this scale, as the bar 
     * rendering starts on the mimimum value and does not go beyond the maximum
     * value.  
     * 
     * @default false
     */
    public function get clipTicks():Boolean
    {
      return _clipTicks;
    }    
    
    /**
     * @private
     */     
    public function set clipTicks(value:Boolean):void
    {
      if (_clipTicks != value) {
        _clipTicks = value;
        invalidateDisplayList();  
      }
    }    

    [Bindable("tickAreaBoundsChanged")]
    
    /**
     * The area reserved for tick drawing in the gauge center area coordinates.
     * You can use this to position other visual elements with respect to the
     * tick area bounds.
     */
    public function get tickAreaBounds():Rectangle
    {
      if (_externalAreaBounds == null)
        _externalAreaBounds = new Rectangle();
      return _externalAreaBounds;
    }       
        
    /**
     * @inheritDoc
     */    
    protected override function get displayOffset():Number
    {
      var offset:Number = _clipTicks ? 0 : getMajorTickWidth() / 2;      
      return (gauge.direction == "horizontal") ? 
        _tickAreaBounds.x + offset : 
        (unscaledHeight - (_tickAreaBounds.y + _tickAreaBounds.height)) + offset;        
    }
    
    /**
     * @inheritDoc
     */       
    protected override function get displayLength():Number 
    {
      var offset:Number = _clipTicks ? 0 : getMajorTickWidth();      
      return (gauge.direction == "horizontal") ? 
        _tickAreaBounds.width - offset : _tickAreaBounds.height - offset;
    }
    
    /**
     * @private
     */
    public override function styleChanged(styleProp:String):void
    {
      super.styleChanged(styleProp);
      if (styleProp != null && styleProp.indexOf("font") != -1) {
        invalidateSize();
      }
    }
    
    /**
     * @private
     */
    protected override function createChildren():void
    {
      if (_ticksHolder == null) {
        _ticksHolder = new UIComponent();
        addChild(_ticksHolder);
      }
    }    
    
    /**
     * Draws the scale.
     *
     * @param unscaledWidth Specifies the width, in pixels, of the component
     * in the component's coordinates, regardless of the value of the
     * <code>scaleX</code> property of the component.
     *
     * @param unscaledHeight Specifies the height, in pixels, of the component
     * in the component's coordinates, regardless of the value of the
     * <code>scaleY</code> property of the component. 
     */         
    protected override function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void 
    {
      // do the layout first, because super.updateDisplayList will call
      // offset & length that need it
      var oldBounds:Rectangle = tickAreaBounds;
      computeLayout(unscaledWidth, unscaledHeight);
      computeTickAreaBounds();      
      // call super in case we are the master to update the scale     
      super.updateDisplayList(unscaledWidth, unscaledHeight);
      if (!oldBounds.equals(tickAreaBounds)) {
        dispatchEvent(new Event("tickAreaBoundsChanged"));
      }                 
      
      // do not draw if not enough room...
      if (gauge.direction == "horizontal") {        
        if (unscaledHeight < getMajorTickLength() || unscaledHeight < getMinorTickLength() ||
            unscaledWidth < getMajorTickWidth() || unscaledWidth < getMinorTickWidth()) {
          clearDisplay();
          return;      
        }  
      } else {
        if (unscaledHeight < getMajorTickWidth() || unscaledHeight < getMinorTickWidth() ||
            unscaledWidth < getMajorTickLength() || unscaledWidth < getMinorTickLength()) {
          clearDisplay();
          return;      
        }
      }
      if (getStyle("showLabels") == true && scale.majorTickItems.length > 0) {
        var lF:Function = labelFunction != null ? labelFunction : defaultLabelFunction;        
        if (_measuringLabel == null)
          createMeasuringLabel();
        
        setMeasuringLabelProperties(lF(scale.majorTickItems[0]), determineTextFormatFromStyles()); 
        if (unscaledWidth < _measuringLabel.measuredWidth ||
            unscaledHeight < _measuringLabel.measuredHeight) {
          clearDisplay();
          return; 
        }
      }      
      
      if (_clipTicks) {
        if (_ticksHolder.mask == null) {
          var mask:FlexShape = new FlexShape();
          mask.name = "mask";
          _ticksHolder.mask = mask;        
          var g:Graphics = mask.graphics;          
          g.beginFill(0xffffff);
          g.drawRect(0, 0, 10, 10);
          g.endFill();
          _ticksHolder.addChild(mask);                            
        }
        _ticksHolder.mask.x = _tickAreaBounds.x;
        _ticksHolder.mask.y = _tickAreaBounds.y;
        _ticksHolder.mask.width = _tickAreaBounds.width;
        _ticksHolder.mask.height = _tickAreaBounds.height;
      } else if (_ticksHolder.mask != null) {
        _ticksHolder.removeChild(_ticksHolder.mask);
        _ticksHolder.mask = null;
      }  
      _ticksHolder.setActualSize(unscaledWidth, unscaledHeight);          
            
      var horizontal:Boolean = gauge.direction == "horizontal";
      var delta:Number = displayOffset;
      var length:Number = displayLength;     
      
      // background
      if (getStyle("backgroundColor")) {
        graphics.beginFill(getStyle("backgroundColor"), getStyle("backgroundAlpha"));
        graphics.drawRect(_tickAreaBounds.x, _tickAreaBounds.y, _tickAreaBounds.width, _tickAreaBounds.height);
        graphics.endFill();
      }
            
      // draw the actual scale
      var twidth:Number;
      var tlength:Number;
      var ticks:Array;
      var instance:DisplayObject;
      var i:int;
      var tickStyleName:String;
      var tickCount:Number;
      var tickDelta:Number;
      var showTicks:Boolean;

      // major ticks & labels
      showTicks = (getStyle("showMajorTicks") == true);
      var showLabels:Boolean = (getStyle("showLabels") == true);
      if ((showTicks || showLabels) && scale.majorTickItems.length > 0) {
        var labelGap:Number = getStyle("labelGap");      
        twidth = getMajorTickWidth();
        tlength = getMajorTickLength();      
        ticks = scale.majorTickItems; 
        tickCount = ticks.length;     
        if (_majorTicksCache == null) {
          _majorTicksCache = new InstanceCache(majorTickRenderer, _ticksHolder);
        } else {
          _majorTicksCache.factory = majorTickRenderer;
        }
        _majorTicksCache.count = tickCount;
        tickStyleName = getStyle("majorTickStyleName");
        if (horizontal) {
          switch (tickPlacement) {
            case "leading":
              tickDelta = 0;
            break;
            case "trailing":
              tickDelta = _tickAreaBounds.height - tlength;
            break;
            case "middle":
            default:
              tickDelta = (_tickAreaBounds.height - tlength) / 2;
            break;  
          }
          tickDelta += _tickAreaBounds.y;
        } else {
          switch (tickPlacement) {
            case "leading":
              tickDelta = 0;
            break;
            case "trailing":
              tickDelta = _tickAreaBounds.width - tlength;
            break;
            case "middle":
            default:
              tickDelta = (_tickAreaBounds.width - tlength) / 2;
            break;  
          }     
          tickDelta += _tickAreaBounds.x;   
        }
        var label:IFlexDisplayObject;      
        var onlyMinMaxLabels:Boolean = getStyle("onlyMinMaxLabels");
        if (showLabels) {         
          fillLabelsCache(ticks, onlyMinMaxLabels);           
        } else {
          if (_labelsCache != null)       
            _labelsCache.count = 0;
        }           
        // label cache instance counter
        var j:int = 0;
        var labelx:Number, labely:Number;
        for (i = 0; i < tickCount; i++) {
          instance = _majorTicksCache.instances[i];
          if (instance is IDataRenderer)
            IDataRenderer(instance).data = ticks[i];
          ISimpleStyleClient(instance).styleName = tickStyleName;        
          instance.width = horizontal ? twidth : tlength;       
          instance.height = horizontal ? tlength : twidth; 
          if (horizontal) {
            instance.x = delta + TickItem(ticks[i]).position * length - twidth / 2;
            instance.y = tickDelta;
            // label
            if (showLabels && (!onlyMinMaxLabels || i == 0 || i == tickCount - 1)) {
              label = _labelsCache.instances[j++];     
              labelx = delta + TickItem(ticks[i]).position * length - label.measuredWidth / 2;               
              if (labelPlacement == "trailing") {
                labely = _tickAreaBounds.height + labelGap;
              } else {
                labely = _tickAreaBounds.y - label.measuredHeight - labelGap;  
              }
              label.move(labelx, labely);
              if (label is UITextField)
                label.setActualSize(measureText(UITextField(label).text).width + 5, UITextField(label).measuredHeight);
              else
              {
                label.setActualSize(label.measuredWidth, label.measuredHeight);
              }              
            }
          } else {
            // vertical
            instance.x = tickDelta;     
            instance.y = (unscaledHeight - (delta + TickItem(ticks[i]).position * length)) - twidth / 2;
            // label
            if (showLabels && (!onlyMinMaxLabels || i == 0 || i == tickCount - 1)) {
              label = _labelsCache.instances[j++];     
              if (label is UITextField)
              {
                var lm:TextLineMetrics = UITextField(label).getLineMetrics(0);              
                labely = (unscaledHeight - (delta + TickItem(ticks[i]).position * length)) - lm.height / 2 - lm.leading;
              }
              else
                labely = (unscaledHeight - (delta + TickItem(ticks[i]).position * length) - label.measuredHeight / 2);
              if (labelPlacement == "trailing") {
                labelx = _tickAreaBounds.width + labelGap;
              } else {
                labelx = _tickAreaBounds.x - label.measuredWidth - labelGap;
              }      
              label.move(labelx, labely);
              label.setActualSize(label.measuredWidth, label.measuredHeight);
            }     
          }
        }
        // we just temporarily built the ticks for the labels, remove them
        if (!showTicks) {
          if (_majorTicksCache != null)
            _majorTicksCache.count = 0;          
        }
      } else {
        // do not show them
        if (_majorTicksCache != null)
          _majorTicksCache.count = 0;
      }

      // minor ticks
      showTicks = (getStyle("showMinorTicks") == true);
      if (showTicks) {           
        twidth = getMinorTickWidth();
        tlength = getMinorTickLength();      
        ticks = scale.minorTickItems;
        if (ticks == null)
          return;
        tickCount = ticks.length;
        if (_minorTicksCache == null) {
          _minorTicksCache = new InstanceCache(minorTickRenderer, _ticksHolder);
        } else {
          _minorTicksCache.factory = minorTickRenderer;
        }            
        _minorTicksCache.count = tickCount;         
        tickStyleName = getStyle("minorTickStyleName");
        if (horizontal) {
          switch (tickPlacement) {
            case "leading":
              tickDelta = 0;
            break;
            case "trailing":
              tickDelta = _tickAreaBounds.height - tlength;
            break;
            case "middle":
            default:
              tickDelta = (_tickAreaBounds.height - tlength) / 2;
            break;  
          }
          tickDelta += _tickAreaBounds.y;            
        } else {
          switch (tickPlacement) {
            case "leading":
              tickDelta = 0;
            break;
            case "trailing":
              tickDelta = _tickAreaBounds.width - tlength;
            break;
            case "middle":
            default:
              tickDelta = (_tickAreaBounds.width - tlength) / 2;
            break;  
          }
          tickDelta += _tickAreaBounds.x;            
        }              
        for (i = 0; i < tickCount; i++) {
          instance = _minorTicksCache.instances[i];
          if (instance is IDataRenderer)
            IDataRenderer(instance).data = ticks[i];
          ISimpleStyleClient(instance).styleName = tickStyleName;
          IFlexDisplayObject(instance).setActualSize(horizontal ? twidth : tlength, horizontal ? tlength : twidth);        
          if (gauge.direction == "horizontal") {
            instance.x = delta + TickItem(ticks[i]).position * length - twidth / 2;
            instance.y = tickDelta;
          } else {
            // vertical
            instance.x = tickDelta;           
            instance.y = (unscaledHeight - (delta + TickItem(ticks[i]).position * length)) - twidth / 2;        
          }
        } 
      } else {
        // do not show them        
        if (_minorTicksCache != null)
          _minorTicksCache.count = 0;
      }
    } 
    
    /**
     * @private
     */
    protected override function measure():void
    {     
      super.measure();
      
      var horizontal:Boolean = (gauge.direction == "horizontal");
      
      var ticks:Array = scale.majorTickItems;      
  
      if (getStyle("showLabels") == true && ticks.length > 0) {
        
        var labelGap:Number = getStyle("labelGap")        

        var lF:Function = labelFunction != null ? labelFunction : defaultLabelFunction;        
        if (_measuringLabel == null)
          createMeasuringLabel();
        
        var height:Number = 0;
        var width:Number = 0;                  
        
        var showOnlyMinMax:Boolean = getStyle("onlyMinMaxLabels");                                 
        
        if (showOnlyMinMax) {
          setMeasuringLabelProperties(lF(ticks[0]), determineTextFormatFromStyles());
          if (horizontal) {
            // + 2 to ask for a bit more room
            width += _measuringLabel.measuredWidth + 2;       
            height = Math.max(height, _measuringLabel.measuredHeight);       
          } else {
            // vertical
            height += _measuringLabel.measuredHeight + 2;            
            width = Math.max(width, _measuringLabel.measuredWidth);            
          }
          setMeasuringLabelProperties(lF(ticks[ticks.length - 1]), determineTextFormatFromStyles());
          if (horizontal) {
            // + 2 to ask for a bit more room
            width += _measuringLabel.measuredWidth + 2;       
            height = Math.max(height, _measuringLabel.measuredHeight);       
          } else {
            // vertical
            height += _measuringLabel.measuredHeight + 2;            
            width = Math.max(width, _measuringLabel.measuredWidth);            
          }                    
        } else {
          for each (var tick:TickItem in ticks) {
            setMeasuringLabelProperties(lF(tick), determineTextFormatFromStyles());
            if (horizontal) {
              // + 2 to ask for a bit more room
              width += _measuringLabel.measuredWidth + 2;       
              height = Math.max(height, _measuringLabel.measuredHeight);       
            } else {
              // vertical
              height += _measuringLabel.measuredHeight + 2;            
              width = Math.max(width, _measuringLabel.measuredWidth);            
            }
          } 
        }    
        if (horizontal) {
          height += labelGap;
          measuredHeight = height + 20;
          measuredMinHeight = height;
          measuredWidth = width;
          measuredMinWidth = 50;
        } else {
          width += labelGap;
          measuredWidth = width + 20;
          measuredMinWidth = width;
          measuredHeight = height;
          measuredMinHeight = 50;
        }

      }
        
      var tickMeasuredWidth:Number = 0;
      var tickMeasuredMinWidth:Number = 0;
      var tickMeasuredHeight:Number = 0;
      var tickMeasuredMinHeight:Number = 0;
      if (horizontal) {
        if (getStyle("showMajorTicks") == true) {
          if (isNaN(_majorTickLength)) {
            tickMeasuredHeight = 20;
            tickMeasuredMinHeight = 5;
          } else {
            tickMeasuredHeight = tickMeasuredMinHeight = _majorTickLength; 
          }
          if (isNaN(_majorTickWidth)) {
            tickMeasuredWidth = 75;
            tickMeasuredMinWidth = 50;
          } else {
            tickMeasuredWidth = tickMeasuredMinWidth = _majorTickWidth * scale.majorTickItems.length;
          }                       
        }
        if (getStyle("showMinorTicks") == true) {
          if (isNaN(_minorTickLength)) {
            tickMeasuredHeight = Math.max(20, tickMeasuredHeight);
            tickMeasuredMinHeight = Math.max(5, tickMeasuredMinHeight);
          } else {
            tickMeasuredHeight = tickMeasuredMinHeight = Math.max(_minorTickLength, tickMeasuredHeight); 
          }   
          if (!isNaN(_minorTickWidth) && scale.minorTickItems != null) {
            tickMeasuredWidth += _minorTickWidth * scale.minorTickItems.length;
            tickMeasuredMinWidth = tickMeasuredWidth;
          }                
        }
        measuredWidth = Math.max(measuredWidth, tickMeasuredWidth);
        measuredMinWidth = Math.max(measuredMinWidth, tickMeasuredWidth);
        measuredHeight += tickMeasuredHeight;
        measuredMinHeight += tickMeasuredMinHeight;
      } else {
        if (getStyle("showMajorTicks") == true) {
          if (isNaN(_majorTickLength)) {
            tickMeasuredWidth = 20;
            tickMeasuredMinWidth = 5;
          } else {
            tickMeasuredWidth = tickMeasuredMinWidth = _majorTickLength; 
          } 
          if (isNaN(_majorTickWidth)) {
            tickMeasuredHeight = 75;
            tickMeasuredMinHeight = 50;
          } else {
            tickMeasuredHeight = tickMeasuredMinHeight = _majorTickWidth * scale.majorTickItems.length;
          }                                  
        }
        if (getStyle("showMinorTicks") == true) {
          if (isNaN(_minorTickLength)) {
            tickMeasuredWidth = Math.max(20, tickMeasuredWidth);
            tickMeasuredMinWidth = Math.max(5, tickMeasuredWidth);
          } else {
            tickMeasuredWidth = tickMeasuredMinWidth = Math.max(_minorTickLength, tickMeasuredWidth); 
          }
          if (!isNaN(_majorTickWidth) && scale.minorTickItems != null) {
            tickMeasuredHeight += _minorTickWidth * scale.minorTickItems.length;
            tickMeasuredMinHeight = tickMeasuredHeight;
          }                     
        }
        measuredWidth += tickMeasuredWidth;
        measuredMinWidth += tickMeasuredMinWidth;
        measuredHeight = Math.max(measuredHeight, tickMeasuredHeight);        
        measuredMinHeight = Math.max(measuredMinHeight, tickMeasuredMinHeight);          
      }         
    }

    private function setMeasuringLabelProperties(text:String, format:UITextFormat = null):void
    {
      if (_measuringLabel is UITextField)
      {
        var tf:UITextField = UITextField(_measuringLabel);
        tf.text = text;
        tf.setTextFormat(format);
      }
      else
      {
        IDataRenderer(_measuringLabel).data = text;
        if (_measuringLabel is ILayoutManagerClient)
          (_measuringLabel as ILayoutManagerClient).validateSize(false);
      }
    }
    
    private function createMeasuringLabel():void
    {
      if (_labelRenderer == null)
      {
        _measuringLabel = new UITextField();
      }
      else
      {
        _measuringLabel = IFlexDisplayObject(_labelRenderer.newInstance());
      }
      _measuringLabel.visible = false;
      addChild(_measuringLabel as DisplayObject);

      if (_measuringLabel is ISimpleStyleClient)
        ISimpleStyleClient(_measuringLabel).styleName = this;
    }
    
    private function clearDisplay():void
    {
      if (_majorTicksCache != null)
        _majorTicksCache.count = 0;
      if (_minorTicksCache != null) 
        _minorTicksCache.count = 0;
      if (_labelsCache != null)
        _labelsCache.count = 0;      
    }
    
    private function computeTickAreaBounds():void
    {
      _externalAreaBounds = (gauge.direction == "horizontal") ?
        new Rectangle(x, y + _tickAreaBounds.y,
                      unscaledWidth, _tickAreaBounds.height) :
        new Rectangle(x + _tickAreaBounds.x, y,
                      _tickAreaBounds.width, unscaledHeight);
    }    
    
    /**
     * Computes the size that the rectangular scale drawing must occupy to leave
     * room for labels.
     */
    private function computeLayout(unscaledWidth:Number, unscaledHeight:Number):void
    {              
       var horizontal:Boolean = (gauge.direction == "horizontal");
  
      if (getStyle("showLabels") == false || scale.majorTickItems.length == 0) {
        _tickAreaBounds = new Rectangle(0, 0, unscaledWidth, unscaledHeight);   
      } else {
        
        var labelGap:Number = getStyle("labelGap")        

        var lF:Function = labelFunction != null ? labelFunction : defaultLabelFunction;        
        if (_measuringLabel == null)
          createMeasuringLabel();
        
        var ticks:Array = scale.majorTickItems;                                      
        setMeasuringLabelProperties(lF(ticks[0]), determineTextFormatFromStyles());
        var swidth:Number = _measuringLabel.measuredWidth;
        var sheight:Number = _measuringLabel.measuredHeight;
        setMeasuringLabelProperties(lF(ticks[ticks.length - 1]), determineTextFormatFromStyles());        
        var ewidth:Number = _measuringLabel.measuredWidth;
        var eheight:Number = _measuringLabel.measuredHeight;    
        
        var fontAreaSize:Number = (horizontal ? Math.max(sheight, eheight) + labelGap : 
                                                Math.max(swidth, ewidth)) + labelGap;
              
        // start and end labels are the one of interest        
        if (horizontal) {
          if (labelPlacement == "trailing") {
            _tickAreaBounds = new Rectangle(swidth / 2, 0, 
                                 unscaledWidth - swidth / 2 - ewidth / 2, 
                                 unscaledHeight - fontAreaSize);
          } else {
            // leading
            _tickAreaBounds = new Rectangle(swidth / 2, fontAreaSize, 
                                 unscaledWidth - swidth / 2 - ewidth / 2, 
                                 unscaledHeight - fontAreaSize);            
          }
        } else {
          // TODO improve because just using start & end to compute the max with is not enought
          var maxWidth:Number = Math.max(swidth, ewidth);
          if (labelPlacement == "trailing") {
            _tickAreaBounds = new Rectangle(0, eheight / 2, 
                                 unscaledWidth - fontAreaSize, 
                                 unscaledHeight - eheight / 2 - ewidth / 2);
          } else {
            // leading
            _tickAreaBounds = new Rectangle(fontAreaSize, eheight / 2, 
                                 unscaledWidth - fontAreaSize, 
                                 unscaledHeight - eheight / 2 - ewidth / 2);            
          }
        }
        
        // final check
        _tickAreaBounds.width = Math.max(_tickAreaBounds.width, 0);
        _tickAreaBounds.height = Math.max(_tickAreaBounds.height, 0);
      }
    }
                  
    /**
     *  @private
     */
    override protected function commitProperties():void
    {
      super.commitProperties();
      textFieldFactory =  new ContextualClassFactory(UITextField, moduleFactory);

      if (_labelsCache == null) {
        _labelsCache = new InstanceCache(getLabelRenderer(), this);
      }
      else
      {
        _labelsCache.count = 0;
        _labelsCache.factory = getLabelRenderer();
      }
    }

                  
    private function fillLabelsCache(instances:Array, onlyMinMaxLabels:Boolean):void
    {
      if (_labelsCache == null) {
        _labelsCache = new InstanceCache(getLabelRenderer(), this);
      }
      _labelsCache.count = onlyMinMaxLabels ? 2 : instances.length;
      var labels:Array = _labelsCache.instances;
      var lF:Function = labelFunction != null ? labelFunction : defaultLabelFunction;
      var dataField:String = getLabelRenderer() == textFieldFactory ? "text" : "data";
      if (onlyMinMaxLabels) {
        labels[0][dataField] = lF(instances[0]);
        if (_labelsCache.factory is UITextField)
        {
          labels[0].setTextFormat(determineTextFormatFromStyles());
          labels[0].ignorePadding = true;
        }
        labels[1][dataField] = lF(instances[instances.length - 1]);
        if (_labelsCache.factory is UITextField)
        {
          labels[1].setTextFormat(determineTextFormatFromStyles());
          labels[1].ignorePadding = true;
        }
      } else {      
        for (var i:int = 0; i < instances.length; i++) {
          labels[i][dataField] = lF(instances[i]);
          if (_labelsCache.factory is UITextField)
          {
            labels[i].setTextFormat(determineTextFormatFromStyles());
            labels[i].ignorePadding = true;
          }
        }
      }  
    }                  
  }    
}
